package cn.lg.soar.common.data;

import java.util.Arrays;

/**
 * 2位集合（能表示 0 到 3）
 * 为解决 BitSet 只能记录两种状态设计，可支持4种状态
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class TwoBitSet implements java.io.Serializable {

    private final static int[] CLEAR = new int[]{ 0x3f, 0xcf, 0xf3, 0xfc };

    private byte[] bytes;

    /**
     * 获取指定索引位置的值
     * @param index
     * @return
     */
    public TwoBitValue get(int index) {
        return TwoBitValue.valueOf(getAsInt(index));
    }

    /**
     * 获取指定索引位置的int值
     * @param index
     * @return
     */
    public int getAsInt(int index) {

        int i = index >>> 2;
        if (i >= bytes.length) {
            enlargeSize(i + 1);
        }
        int mc = (3 - (index & 0x03)) << 1;
        return (bytes[ i ] >>> mc) & 0x03;
    }

    /**
     * 设置指定索引位置的值
     * @param index
     */
    public void set(int index, TwoBitValue value) {
        int i = index >>> 2;
        if (i >= bytes.length) {
            enlargeSize(i + 1);
        }
        index &= 0x03;
        bytes[ i ] = (byte) ((bytes[ i ] & CLEAR[ index ]) | (value.value() << (6 - (index << 1))));
    }

    /**
     * 通过int值设置指定索引位置的值
     * @param index
     */
    public void setByInt(int index, int value) {
        set(index, TwoBitValue.valueOf(value));
    }

    /**
     * 返回第一个为 0 的索引
     * @return
     */
    public int firstZero(){
        for (int i = 0; i < bytes.length; i++) {
            byte aByte = bytes[i];
            if ((aByte & 0xc0) == 0) return (i << 2);
            if ((aByte & 0x30) == 0) return (i << 2) + 1;
            if ((aByte & 0x0c) == 0) return (i << 2) + 2;
            if ((aByte & 0x03) == 0) return (i << 2) + 3;
        }
        return (bytes.length << 2) + 1;
    }

    /**
     * 统计所有不为 0 的个数
     * @return
     */
    public int cardinality() {
        int count = 0;
        for (byte b : bytes) {
            if (b == 0) {
                continue;
            }
            if (b == -1) {
                count += 4;
                continue;
            }
            if ((b & 0xc0) != 0) count++;
            if ((b & 0x30) != 0) count++;
            if ((b & 0x0c) != 0) count++;
            if ((b & 0x03) != 0) count++;
        }
        return count;
    }

    /**
     * 返回此 TwoBitSet 最小占用空间的个数
     * @return
     */
    public int size() {
        int len = bytes.length;
        if (len == 1) {
            return len << 2;
        }
        while (--len > 0 && bytes[ len ] == 0);
        return (len + 1) << 2;
    }

    /**
     * 返回此 TwoBitSet 实际占用空间的个数
     * @return
     */
    public int length() {
        return bytes.length << 2;
    }

    /**
     * 缩小此 TwoBitSet 实际占用空间到最小占用空间
     */
    public void reduceSize() {
        byte[] bs = new byte[ size() >>> 2 ];
        for (int j = 0; j < bs.length; j++) {
            bs[ j ] = bytes[ j ];
        }
        bytes = bs;
    }

    public byte[] toByteArray(){
        return bytes.clone();
    }

    public TwoBitSet clone() {
        return new TwoBitSet(bytes.clone());
    }

    @Override
    public String toString() {
        return new String(bytes);
    }

    public TwoBitSet() {
        this.bytes = new byte[ 1 ];
    }

    public TwoBitSet(int initLength) {
        this.bytes = new byte[ initLength >> 3 ];
    }

    public TwoBitSet(byte[] bytes) {
        this.bytes = bytes;
    }

    public TwoBitSet(String str) {
        this.bytes = str.getBytes();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TwoBitSet bitSet = (TwoBitSet) o;
        return Arrays.equals(bytes, bitSet.bytes);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(bytes);
    }

    /**
     * 扩大此 TwoBitSet 空间
     * @param newLen
     */
    private void enlargeSize(int newLen) {
        byte[] bs = new byte[ newLen ];
        for (int j = 0; j < bytes.length; j++) {
            bs[ j ] = bytes[ j ];
        }
        bytes = bs;
    }

    /**
     * 判断 TwoBitSet 是否为空
     * @param twoBitSet
     * @return
     */
    public static boolean isEmpty(TwoBitSet twoBitSet){
        return twoBitSet != null && twoBitSet.size() == 4 && twoBitSet.bytes[ 0 ] == 0;
    }

    public static int get(byte[] bytes, int index){
        return ((bytes[ index >>> 2 ] >>> ((3 - (index & 0x03)) << 1)) & 0x03);
    }

    public static void set(byte[] bytes, int index, int value){
        TwoBitValue twoBitValue = TwoBitValue.valueOf(value);
        int i = index >>> 2;
        index &= 0x03;
        bytes[ i ] = (byte) ((bytes[ i ] & CLEAR[ index ]) | (twoBitValue.value() << (6 - (index << 1))));
    }

    public enum TwoBitValue {
        ZERO,ONE,TWO,THREE;

        public int value(){
            return this.ordinal();
        }

        public static TwoBitValue valueOf(int i){
            switch (i) {
                case 0:  return TwoBitValue.ZERO;
                case 1:  return TwoBitValue.ONE;
                case 2:  return TwoBitValue.TWO;
                case 3: return TwoBitValue.THREE;
                default: throw new RuntimeException("value out of range, Optional value: 0 1 2 3");
            }
        }
    }
}
